﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using MySql.Data.MySqlClient;

namespace DBTools
{
    public partial class Main : Form
    {
        private readonly string _iniPath = Application.StartupPath + "/setting.ini";
        private string chk_database = string.Empty;
        public Main()
        {
            InitializeComponent();

            btn_conn.Click += Btn_conn_Click;
            cbox_database.SelectedIndexChanged += Cbox_database_SelectedIndexChanged;
            btn_checkdir.Click += Btn_checkdir_Click;
            btn_cthis.Click += Btn_cthis_Click;
            btn_call.Click += Btn_call_Click;

            StringBuilder sbdr = new StringBuilder(150);
            GetPrivateProfileString("setting", "host", "", sbdr, 1024, _iniPath);
            if (sbdr.Length > 0) txt_host.Text = sbdr.ToString();

            GetPrivateProfileString("setting", "user", "", sbdr, 1024, _iniPath);
            if (sbdr.Length > 0) txt_user.Text = sbdr.ToString();

            GetPrivateProfileString("setting", "passwd", "", sbdr, 1024, _iniPath);
            if (sbdr.Length > 0) txt_passwd.Text = sbdr.ToString();

            GetPrivateProfileString("setting", "dir", "", sbdr, 1024, _iniPath);
            if (sbdr.Length > 0) cbox_savepath.DataSource = sbdr.ToString().Split(';').ToList();

            GetPrivateProfileString("setting", "namespace", "", sbdr, 1024, _iniPath);
            if (sbdr.Length > 0) cbox_namespace.DataSource = sbdr.ToString().Split(';').ToList();

            GetPrivateProfileString("setting", "database", "", sbdr, 1024, _iniPath);
            if (sbdr.Length > 0) chk_database = sbdr.ToString();

            CheckForIllegalCrossThreadCalls = false;
            if (!string.IsNullOrWhiteSpace(txt_host.Text) && !string.IsNullOrWhiteSpace(txt_user.Text) && !string.IsNullOrWhiteSpace(txt_passwd.Text))
                Task.Run(() => InitDataBase());
        }

        private void Cbox_database_SelectedIndexChanged(object sender, EventArgs e)
        {
            string dbName = ((ComboBox)sender).SelectedValue.ToString();
            DB.SetDataBase(dbName);

            cbox_tabls.DataSource = DB.ExecuteToList($"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.`TABLES` WHERE TABLE_SCHEMA = '{dbName}' ORDER BY CREATE_TIME; ");
        }

        private void InitDataBase()
        {
            try
            {
                DB.Init(txt_host.Text, txt_user.Text, txt_passwd.Text);
                //DB.Init("host=192.168.1.32;user=root;port=8653;password=Kefang80123!!;Allow User Variables=True;Charset=utf8;database=KeFangPublicity;");
                cbox_database.DataSource = DB.ExecuteToList("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.`SCHEMATA` WHERE SCHEMA_NAME NOT IN ('information_schema','performance_schema','mysql') ORDER BY SCHEMA_NAME;");
                if (!string.IsNullOrWhiteSpace(chk_database))
                {
                    cbox_database.SelectedIndex = cbox_database.Items.IndexOf(chk_database);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(@"连接数据库失败" + ex.Message, @"提示", MessageBoxButtons.OK);
                return;
            }
            WritePrivateProfileString("setting", "host", txt_host.Text, _iniPath);
            WritePrivateProfileString("setting", "user", txt_user.Text, _iniPath);
            WritePrivateProfileString("setting", "passwd", txt_passwd.Text, _iniPath);
        }

        private void Btn_call_Click(object sender, EventArgs e)
        {
            WritePrivateProfileString("setting", "database", cbox_database.Text, _iniPath);
            WritePrivateProfileString("setting", "namespace", cbox_namespace.Text, _iniPath);

            RemoveNotExistsFile();
            SaveAllFile();
            Process.Start(cbox_savepath.Text);
            SaveIni();
        }

        private void Btn_cthis_Click(object sender, EventArgs e)
        {
            WritePrivateProfileString("setting", "database", cbox_database.Text, _iniPath);
            WritePrivateProfileString("setting", "namespace", cbox_namespace.Text, _iniPath);

            SaveFile();
            Process.Start(cbox_savepath.Text);
            SaveIni();
        }

        private void Btn_checkdir_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog fbd = new FolderBrowserDialog();
            if (fbd.ShowDialog() != DialogResult.OK) return;

            cbox_savepath.Text = fbd.SelectedPath;
        }

        private void Btn_conn_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txt_user.Text) || string.IsNullOrWhiteSpace(txt_passwd.Text))
            {
                MessageBox.Show(@"用户名和密码", @"提示", MessageBoxButtons.OK);
                return;
            }
            Task.Run(() => InitDataBase());
        }

        private void SaveFile()
        {
            string tableName = cbox_tabls.SelectedValue.ToString();

            string filePath = GetFilePath(tableName);

            string notMappedProprety = GetNotMappedProprety(tableName, filePath);

            string tableComment = DB.ExecuteScalar($"SELECT TABLE_COMMENT FROM INFORMATION_SCHEMA.`TABLES` WHERE TABLE_SCHEMA = '{DB.DataBase}' AND `TABLE_NAME` = '{tableName}'") as string;
            DataTable dt = DB.ExecuteToDataTable($"SELECT COLUMN_NAME,DATA_TYPE,COLUMN_COMMENT,COLUMN_KEY,EXTRA,IS_NULLABLE,COLUMN_DEFAULT FROM information_schema.`COLUMNS` WHERE TABLE_SCHEMA = '{DB.DataBase}' AND TABLE_NAME = '{tableName}'");
            File.WriteAllText(filePath, DrawModelClass(tableComment, tableName, dt, notMappedProprety), Encoding.UTF8);
        }

        private string GetNotMappedProprety(string tableName, string filePath)
        {
            string notMappedProprety = string.Empty;
            if (File.Exists(filePath))
            {
                notMappedProprety = GetNotMappedProprety(File.ReadAllText(filePath));
                File.Delete(filePath);
            }
            return notMappedProprety;
        }

        private string GetFilePath(string tableName)
        {
            if (!Directory.Exists(cbox_savepath.Text))
            {
                Directory.CreateDirectory(cbox_savepath.Text);
            }
            return cbox_savepath.Text + "\\" + tableName + ".cs";
        }

        private void SaveAllFile()
        {
            DataTable tables = DB.ExecuteToDataTable("SELECT TABLE_COMMENT,TABLE_NAME FROM INFORMATION_SCHEMA.`TABLES` WHERE TABLE_SCHEMA = SCHEMA()");
            DataTable columns = DB.ExecuteToDataTable($"SELECT TABLE_NAME,COLUMN_NAME,DATA_TYPE,COLUMN_COMMENT,COLUMN_KEY,EXTRA,IS_NULLABLE,COLUMN_DEFAULT FROM information_schema.`COLUMNS` WHERE TABLE_SCHEMA = '{DB.DataBase}'");

            DataView dv = columns.DefaultView;

            foreach (DataRow row in tables.Rows)
            {
                string tableName = row["TABLE_NAME"].ToString();
                string tableComment = row["TABLE_COMMENT"].ToString();

                string filePath = GetFilePath(tableName);

                string notMappedProprety = GetNotMappedProprety(tableName, filePath);

                dv.RowFilter = "TABLE_NAME = '" + tableName + "'";

                File.WriteAllText(filePath, DrawModelClass(tableComment, tableName, dv.ToTable(true), notMappedProprety), Encoding.UTF8);
            }
        }

        private void SaveIni()
        {
            List<string> savepathList = cbox_savepath.DataSource == null ? new List<string>() : (cbox_savepath.DataSource as List<string>).ToList();
            savepathList.Insert(0, cbox_savepath.Text);
            savepathList = savepathList.Distinct().Where(a => !string.IsNullOrWhiteSpace(a)).ToList();
            cbox_savepath.DataSource = savepathList;

            List<string> namespaceList = cbox_namespace.DataSource == null ? new List<string>() : (cbox_namespace.DataSource as List<string>).ToList();
            namespaceList.Insert(0, cbox_namespace.Text);
            namespaceList = namespaceList.Distinct().Where(a => !string.IsNullOrWhiteSpace(a)).ToList();
            cbox_namespace.DataSource = namespaceList;

            WritePrivateProfileString("setting", "dir", string.Join(";", savepathList).Trim(';'), _iniPath);
            WritePrivateProfileString("setting", "namespace", string.Join(";", namespaceList).Trim(';'), _iniPath);
        }

        private void RemoveNotExistsFile()
        {
            List<string> files = Directory.GetFiles(cbox_savepath.Text).Select(a => Path.GetFileNameWithoutExtension(a)).ToList();
            List<string> tableNames = cbox_tabls.DataSource as List<string>;

            List<string> deleteFiles = files.Except(tableNames).Select(a => cbox_savepath.Text + "\\" + a + ".cs").ToList();

            deleteFiles.ForEach(File.Delete);
        }

        private string DrawModelClass(string tableComment, string tableName, DataTable dt, string notMappedPropertyText)
        {
            StringBuilder sbdr = new StringBuilder(Convert.ToInt32(dt.Rows.Count * Math.Pow(10, 4)));
            //sbdr.AppendLine("using System;");
            sbdr.AppendLine("using Chloe.Entity;");
            sbdr.AppendLine($"\r\n namespace {cbox_namespace.Text} \r\n {{");
            if (!string.IsNullOrWhiteSpace(tableComment))
            {
                sbdr.AppendLine("    /// <summary>");
                sbdr.AppendLine($"    /// {tableComment.Replace("\r\n", "\r\n    /// ")}");
                sbdr.AppendLine("    /// </summary>");
            }
            sbdr.AppendLine($"    [Table(\"{tableName}\")]");
            sbdr.AppendLine($"    public partial class {tableName}\r\n    {{");

            sbdr.AppendLine(DrawColumnsModel(dt, out bool isSystem));

            if (isSystem)
                sbdr.Insert(0, "using System;\r\n");
            sbdr.AppendLine(notMappedPropertyText.Trim('\r', '\n'));
            sbdr.AppendLine("    }\r\n}");
            return sbdr.ToString();
        }

        private string DrawColumnsModel(DataTable dt, out bool isSystem)
        {
            StringBuilder sbdr = new StringBuilder(Convert.ToInt32(dt.Rows.Count * Math.Pow(10, 4)));
            StringComparison scpr = StringComparison.OrdinalIgnoreCase;
            List<string> colAttrList = new List<string>();
            isSystem = false;
            foreach (DataRow row in dt.Rows)
            {
                string dataType = row["DATA_TYPE"].ToString();
                bool isNullable = row["IS_NULLABLE"].Equals("YES");
                string type = MapCsharpType(dataType, isNullable);

                if (!isSystem)
                    isSystem = type.StartsWith("DateTime", scpr);

                if (!string.IsNullOrWhiteSpace(row["COLUMN_COMMENT"].ToString()))
                {
                    sbdr.AppendLine("        /// <summary>");
                    sbdr.AppendLine("        /// " + row["COLUMN_COMMENT"]);
                    sbdr.AppendLine("        /// </summary>");
                }
                colAttrList.Clear();

                if (row["COLUMN_KEY"].ToString().Equals("PRI", scpr))
                {
                    colAttrList.Add("Column(IsPrimaryKey = true)");
                }
                if (row["EXTRA"].ToString().Equals("auto_increment", scpr))
                {
                    colAttrList.Add("AutoIncrement");
                }
                if (colAttrList.Any())
                {
                    sbdr.AppendLine($"        [{string.Join(", ", colAttrList)}]");
                }

                sbdr.AppendLine("        public " + type + " " + row["COLUMN_NAME"] + " { get; set; }" + DrawDefaultValue(row["COLUMN_DEFAULT"], type));
            }
            return sbdr.ToString();
        }

        private string DrawDefaultValue(object defValue, string type)
        {
            StringComparison scpr = StringComparison.OrdinalIgnoreCase;
            if (defValue == DBNull.Value)
            {
                return string.Empty;
            }
            StringBuilder sbdr = new StringBuilder(" = ", 20);
            if (", string, object, ".IndexOf(", " + type + ", ", scpr) > -1)
            {
                sbdr.AppendFormat("\"{0}\"", defValue);
            }
            else if (type.Equals("bool", scpr) || type.Equals("bool?", scpr))
            {
                sbdr.Append(defValue.Equals("b'1'").ToString().ToLower());
            }
            else
            {
                sbdr.Append(defValue);
            }
            sbdr.Append(";");
            return sbdr.ToString();
        }

        private string MapCsharpType(string dbtype, bool isNullable)
        {
            if (string.IsNullOrEmpty(dbtype)) return dbtype;
            string csharpType = string.Empty;
            switch (dbtype.ToLower())
            {
                case "bigint": csharpType = "long"; break;
                case "binary":
                case "timestamp":
                case "varbinary":
                case "image": csharpType = "byte[]"; break;
                case "bit":
                case "dityint": csharpType = "bool"; break;
                case "date":
                case "datetime":
                case "datetime2":
                case "smalldatetime": csharpType = "DateTime"; break;
                case "datetimeoffset": csharpType = "DateTimeOffset"; break;
                case "decimal":
                case "money":
                case "numeric":
                case "smallmoney": csharpType = "decimal"; break;
                case "float": csharpType = "double"; break;
                case "tinyint":
                case "int": csharpType = "int"; break;
                case "char":
                case "nchar":
                case "ntext":
                case "varchar":
                case "xml":
                case "nvarchar":
                case "text":
                case "longtext": csharpType = "string"; break;
                case "real": csharpType = "Single"; break;
                case "smallint": csharpType = "short"; break;
                case "time": csharpType = "TimeSpan"; break;
                case "uniqueidentifier": csharpType = "Guid"; break;
                case "double": csharpType = "double"; break;
                default: csharpType = "object"; break;
            }
            return !"string".Equals(csharpType) && isNullable ? csharpType + "?" : csharpType;
        }

        private string GetNotMappedProprety(string text)
        {
            int idx = text.IndexOf("[NotMapped]", 0);
            if (idx < 0) return string.Empty;

            int propertyBeginIdx = text.LastIndexOf('}', idx) + 1;
            string surplus = text.Substring(propertyBeginIdx);
            string property = string.Empty;
            idx = 0;
            int bracketCnt = 0;
            while (true)
            {
                int firstIdx = surplus.IndexOf('{', idx);//第一个括号下标;
                if (idx < 0) break;
                ++bracketCnt;//记录第一个括号
                ++firstIdx;
                if (surplus.IndexOf('{', firstIdx) > -1)
                {
                    idx = surplus.IndexOf('{', firstIdx);
                    string str = surplus.Substring(firstIdx, idx - firstIdx);//第二个括号下标
                    bracketCnt -= str.Length - str.Replace("}", "").Length;//一共有多少右括号
                }
                else
                {
                    idx = firstIdx;
                    do
                    {
                        idx = surplus.IndexOf('}', idx) + 1;
                    } while (--bracketCnt > 0);
                }
                if (bracketCnt < 1)
                {
                    property = surplus.Substring(0, surplus.LastIndexOf('}', idx) + 1).Trim('\r', '\n');
                    break;
                }//判断括号是否匹配
            }

            if (string.IsNullOrWhiteSpace(property)) return string.Empty;
            surplus = surplus.Substring(property.Length);
            // 从第一个{到第二个{中是否有}如果有则不添加{
            StringBuilder sbdr = new StringBuilder((int)2e2);
            sbdr.AppendLine(property);
            string nextPreperty = GetNotMappedProprety(surplus);
            if (!string.IsNullOrWhiteSpace(nextPreperty)) sbdr.AppendLine(nextPreperty);
            return sbdr.ToString();
        }

        /// <summary>
        ///     INI读取
        /// </summary>
        /// <param name="section">要读取的段落名</param>
        /// <param name="key">要读取的键</param>
        /// <param name="defVal">读取异常的情况下的缺省值</param>
        /// <param name="retVal">key所对应的值，如果该key不存在则返回空值</param>
        /// <param name="size">值允许的大小</param>
        /// <param name="filePath">INI文件的完整路径和文件名</param>
        /// <returns></returns>
        [DllImport("kernel32")]
        private static extern int GetPrivateProfileString(string section, string key, string defVal, StringBuilder retVal, int size, string filePath);

        /// <summary>
        ///     INI写
        /// </summary>
        /// <param name="section">要写入的段落名</param>
        /// <param name="key">要写入的键，如果该key存在则覆盖写入</param>
        /// <param name="val">key所对应的值</param>
        /// <param name="filePath">INI文件的完整路径和文件名</param>
        /// <returns></returns>
        [DllImport("kernel32")]
        private static extern long WritePrivateProfileString(string section, string key, string val, string filePath);

    }
}